"""
@Description :
线性回归实现
@Author : inY_Yue
@Time : 2022/4/2710:29
"""
import numpy as np


class LinearRegression(object):
    """
    @ClassName：LinearRegression
    @Description：梯度下降法实现线性回归
    @Author：inY_Yue
    """

    def __init__(self, n_iter=200, eta=1e-3, tol=None):
        self.n_iter = n_iter  # 迭代次数
        self.eta = eta  # 学习率
        self.tol = tol  # 误差变化阈值
        self.w = None  # 模型参数w(训练时初始化)

    def _loss(self, y, y_pred):
        """
        @Description :
        计算损失函数
        @params :
        y - 测试样本数据
        y - 预测值
        """
        return np.sum((y_pred - y) ** 2) / y.size

    def _gradient(self, x, y, y_pred):
        """
        @Description :
        计算梯度
        @params :
        x - 训练样本数据
        y - 测试样本数据
        y_pred - 测试样本预测结果
        """
        return np.matmul(y_pred - y, x) / y.size

    def _gradient_descent(self, w, X, y):
        """
        @Description :
        批量梯度下降算法
        @params :
        """
        if self.tol is not None:  # 若用户执行了精度 tol ，则启用早期停止法
            loss_old = np.inf

        # 使用梯度下降，至多迭代n_iter次，更新w
        for step_i in range(self.n_iter):
            # 预测
            y_pred = self._predict(X, w)
            # 计算损失
            loss = self._loss(y, y_pred)
            print('%4i Loss : %s' % (step_i, loss))

            # 早期停止法
            if self.tol is not None:
                if loss_old - loss < self.tol:
                    break
                loss_old = loss

            # 计算梯度
            grad = self._gradient(X, y, y_pred)
            # 更新参数
            w -= self.eta * grad

    def _preprocess_data_X(self, X):
        """
        @Description :
        数据预处理
        @params :
        X - 原始数据
        @return :
        处理之后的数据
        """
        m, n = X.shape
        X_ = np.empty((m, n + 1))  # 拓展X，添加x0列并设置为1
        X_[:, 0] = 1
        X_[:, 1:] = X

        return X_

    def train(self, X_train, y_train):
        """
        @Description :
        训练模型
        @params :
        X_train - 训练集X
        y_train - 验证集y
        @return :
        """
        X_train = self._preprocess_data_X(X_train)  # 预处理X_train,对x0进行补1
        _, n = X_train.shape
        self.w = np.random.random(n) * 0.05  # 初始化参数向量w

        self._gradient_descent(self.w, X_train, y_train)

    def _predict(self, X, w):
        """
        @Description :
        预测的内部接口，实现函数 hypothesis(x)
        @params :
        X - 原始数据
        w - 初始化的参数矩阵
        """
        return np.matmul(X, w)

    def predict(self, X):
        """
        @Description :
        预测
        @params :
        X - 传入的数据
        """
        X = self._preprocess_data_X(X)
        return self._predict(X, self.w)
